﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using SmlEngine;
using SmlEngine.Timers;
using SmlEngine.UI.Input;
using SmlEngine.UI.Screens;

namespace SmlEngine.UI.Managers
{
    public sealed class ScreenManager : DrawableGameComponent
    {
        #region Fields

        //private List<Screen> screens;
        //private Stack<Screen> screensToUpdate;
        private List<ScreenGroup> groups;
        public int activeGroupIndex { get; private set; }

        private string fontLocation;

        private bool isInitialized;
        private bool isContentLoaded;

        public ResourceState resourceState { get; private set; }

        #endregion

        #region Properties

        public SpriteBatch SpriteBatch { get; private set; }
        public SpriteFont DefaultFont { get; private set; }

        public Vector2 MeasureString(string text)
        {
            return DefaultFont.MeasureString(text);
        }

        public InputManager Input { get; private set; }
        public TimerManager Timers { get; private set; }

        public int Width { get { return Game.GraphicsDevice.Viewport.Width; } }
        public int Height { get { return Game.GraphicsDevice.Viewport.Height; } }

        public Rectangle Bounds
        {
            get { return Game.GraphicsDevice.Viewport.Bounds; }
        }

        public Screen this[int grp_index, int scr_index]
        {
            get { return groups[grp_index][scr_index]; }
        }

        public ICollection<ScreenGroup> ScreenGroupsCollection
        {
            get { return groups; }
        }

        public List<ScreenGroup> ScreenGroups
        {
            get { return groups; }
        }

        #endregion

        #region Methods

        #region Constructors, Initialization, Content Loading and Disposal
        
        /// <summary>
        /// Constructs a new ScreenManager from the specified game.
        /// </summary>
        /// <param name="game">The game to get values such as content from.</param>
        /// <param name="fontLocation">The location of the default font.</param>
        public ScreenManager(Game game, string fontLocation)
            : base(game)
        {
            game.Components.Add(this);
            this.fontLocation = fontLocation;

            groups = new List<ScreenGroup>();
            resourceState = ResourceState.WorldStandalone; // temporary

            Input = new InputManager(Game);
            Timers = new TimerManager(Game);
            Game.Components.Add(Input);
            Game.Components.Add(Timers);
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run. This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            if (!isInitialized)
            {
                isInitialized = true;

                // Initialize each of the screens.
                foreach (ScreenGroup screenGroup in groups)
                {
                    screenGroup.Initialize();
                }

                base.Initialize();
            }
        }

        protected override void LoadContent()
        {
            if (!isInitialized)
                Initialize();

            if (!isContentLoaded)
            {
                ContentManager content = Game.Content;

                SpriteBatch = new SpriteBatch(GraphicsDevice);

                DefaultFont = content.Load<SpriteFont>(fontLocation);

                // Load each of the screen's content.
                foreach (ScreenGroup screenGroup in groups)
                {
                    screenGroup.LoadContent(content);
                }

                isContentLoaded = true;
            }
        }

        protected override void UnloadContent()
        {
            if (isContentLoaded)
            {
                // Unload each of the screen's content.
                foreach (ScreenGroup screenGroup in groups)
                {
                    screenGroup.UnloadContent();
                }

                isContentLoaded = false;
            }
        }

        #endregion

        #region Update

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides information about timing values.</param>
        public override void Update(GameTime gameTime)
        {
            Input.Update(gameTime);

            bool anotherScreenHasFocus = !Game.IsActive;
            bool coveredByAnotherScreen = false;

            foreach (Screen screen in groups[activeGroupIndex].Screens)
            {

                // Update the screen.
                screen.Update(gameTime, anotherScreenHasFocus, coveredByAnotherScreen);

                if (screen.ScreenState == ScreenState.TransitionOn ||
                    screen.ScreenState == ScreenState.Active)
                {
                    // If this is the first active screen we came across,
                    // give it a chance to handle input.
                    if (!anotherScreenHasFocus)
                    {
                        screen.HandleInput(Input);

                        anotherScreenHasFocus = true;
                    }

                    // If this is an active non-popup, inform any subsequent
                    // screens that they are covered by it.
                    if (!screen.IsPopup)
                        coveredByAnotherScreen = true;
                }
            }

            base.Update(gameTime);
        }

        #endregion

        #region Draw

        public override void Draw(GameTime gameTime)
        {
            SpriteBatch.Begin();

            bool isViewportChanged = false;

            foreach (Screen screen in groups[activeGroupIndex].Screens)
            {
                if (screen.ScreenState == ScreenState.Hidden)
                    continue;

                screen.Draw(gameTime);
                isViewportChanged = screen.ViewportChanged;
            }

            //Screens may change the viewport when drawing their component IScenes, so reset the GraphicsDevice.
            if (isViewportChanged)
                GraphicsDevice.SetViewport(ViewportLocation.FullScreen);

            SpriteBatch.End();
        }

        #endregion

        #region Public Methods

        public int AddScreenGroup(ScreenGroup screenGroup)
        {
            foreach (Screen s in screenGroup.Screens)
            {
                s.ScreenManager = this;
                s.IsExiting = false;
            }
            if (isInitialized)
               screenGroup.Initialize();

            if (isContentLoaded)
               screenGroup.LoadContent(Game.Content);

            groups.Add(screenGroup);
            return groups.IndexOf(screenGroup);
        }

        public void RemoveScreenGroup(ScreenGroup screenGroup, int switchIndex)
        {
            if (isContentLoaded)
                screenGroup.UnloadContent();

            groups.Remove(screenGroup);
            SetActiveGroup(switchIndex);
        }

        public void AddScreen(int groupIndex, Screen screen)
        {
            if (!(groupIndex < 0 || groupIndex > groups.Count - 1))
            {
                groups[groupIndex].AddScreen(screen);
            }
        }

        public void RemoveScreen(Screen screen)
        {
            foreach (ScreenGroup g in groups)
            {
                if (g.Screens.Contains(screen))
                {
                    g.RemoveScreen(screen);
                    return;
                }
            }
        }

        public void RemoveAllScreenGroups()
        {
            foreach (ScreenGroup s in groups)
            {
                if (isContentLoaded)
                    s.UnloadContent();
            }
            groups.Clear();
        }

        public void SetActiveGroup(int index)
        {
            if (!(index < 0 || index > groups.Count - 1))
            {
                activeGroupIndex = index;
            }
            else
            {
                throw new ArgumentOutOfRangeException("index", string.Format("Screen group index {0} was out of range. (max index: {1})", index.ToString(), (groups.Count - 1).ToString()));
            }
        }

        public int GroupIndexFromScreen(Screen screen)
        {
            int i = 0;
            foreach (ScreenGroup s in groups)
            {
                if (s.Screens.Contains(screen)) return i;
                i++;
            }
            return -1;
        }

        public void FadeBackBufferToBlack(float alpha)
        {
            SpriteBatch.Draw(GraphicsDevice.GetBlankTexture(),
                             new Rectangle(0, 0, Width, 
                             Height), Color.Black * alpha);
        }

        #endregion

        #region Event Handlers
        #endregion

        #endregion
    }
}
